package com.ccx.myinterpreter;
import java.util.ArrayList;
import java.util.List;

/**
 * 词法分析，识别出由字符组成的单词，输入字符串输出token
 * 包括 关键字（var）、变量（a）、操作符（= + - * /）、括号（( ) { }）
 * 单词可由正则表达式表示，有限状态机就可识别
 */
public class Scanner {
    private String src;     // 源代码
    private int current;    // 待读取的字符位置
    private int start;      // 记录单词开始位置
    List<Token> list;

    public Scanner(String src) {
        this.src = src;
        this.current = 0;
        this.list = new ArrayList<>();
    }

    public void scan() {
        while (!isAtEnt()) {
            char c = fetch();
            switch (c) {
                case ';':
                    list.add(new Token(";", TokenType.SEMICOLON, null)); break;
                case ',':
                    list.add(new Token(",", TokenType.COMMA, null)); break;
                case '.':
                    list.add(new Token(".", TokenType.DOT, null)); break;
                case '(':
                    list.add(new Token("(", TokenType.LEFT_PAREN, null)); break;
                case ')':
                    list.add(new Token(")", TokenType.RIGHT_PAREN, null)); break;
                case '{':
                    list.add(new Token("{", TokenType.LEFT_BRACE, null)); break;
                case '}':
                    list.add(new Token("}", TokenType.RIGHT_BRACE, null)); break;
                case '+':
                    list.add(new Token("+", TokenType.PLUS, null)); break;
                case '-':
                    list.add(new Token("-", TokenType.MINUS, null)); break;
                case '*':
                    list.add(new Token("*", TokenType.STAR, null)); break;
                case '/':
                    list.add(new Token("/", TokenType.SLASH, null)); break;
                case '!':
                    if(match('=')) {
                        list.add(new Token("!=", TokenType.BANG_EQUAL, null));
                    } else {
                        list.add(new Token("!", TokenType.BANG, null));
                    }
                    break;
                case '=':
                    if(match('=')) {
                        list.add(new Token("==", TokenType.EQUAL_EQUAL, null));
                    } else {
                        list.add(new Token("=", TokenType.EQUAL, null));
                    }
                    break;
                case '>':
                    if(match('=')) {
                        list.add(new Token(">=", TokenType.GREATER_EQUAL, null));
                    } else {
                        list.add(new Token(">", TokenType.GREATER, null));
                    }
                    break;
                case '<':
                    if(match('=')) {
                        list.add(new Token("<=", TokenType.LESS_EQUAL, null));
                    } else {
                        list.add(new Token("<", TokenType.LESS, null));
                    }
                    break;
                case '"':
                    matchString(); break;
                case ' ':
                case '\n':
                case '\t':
                    break;
            
                default:
                    matchWord(c);
                    break;
            }
        }
    }

    private boolean isAtEnt() {
        return current >= src.length();
    }

    private char fetch() {
        return src.charAt(current++);
    }

    private boolean match(char c) {
        if(src.charAt(current) == c) {
            current++;
            return true;
        }
        return false;
    }

    private void matchString() {
        start = current - 1;
        while (!isAtEnt() && !match('"')) {
            current++;
        }
        if(previous() == '"') {
            list.add(new Token(src.substring(start, current), TokenType.STRING, src.substring(start + 1, current - 1)));
        }
    }

    private boolean isNum(char c) {
        return '0' <= c && c <='9';
    }

    private boolean isAlpha(char c) {
        return '0' <= c && c <='9'
                || 'a' <= c && c <='z'
                || 'A' <= c && c <='Z'
                || c == '_';
    }

    private void matchWord(char c) {
        start = current - 1;
        if(isNum(c)) {
            matchNum();
            return;
        }
        while (!isAtEnt() && isAlpha(peek())) {
            current++;
        }
        String lexeme = src.substring(start, current);
        switch (lexeme) {
            case "var":
                list.add(new Token(lexeme, TokenType.VAR, null)); break;
            case "fun":
                list.add(new Token(lexeme, TokenType.FUN, null)); break;
            case "class":
                list.add(new Token(lexeme, TokenType.CLASS, null)); break;
            case "if":
                list.add(new Token(lexeme, TokenType.IF, null)); break;
            case "else":
                list.add(new Token(lexeme, TokenType.ELSE, null)); break;
            case "for":
                list.add(new Token(lexeme, TokenType.FOR, null)); break;
            case "while":
                list.add(new Token(lexeme, TokenType.WHILE, null)); break;
            case "return":
                list.add(new Token(lexeme, TokenType.RETURN, null)); break;
            case "and":
                list.add(new Token(lexeme, TokenType.AND, null)); break;
            case "or":
                list.add(new Token(lexeme, TokenType.OR, null)); break;
            case "this":
                list.add(new Token(lexeme, TokenType.THIS, null)); break;
            case "super":
                list.add(new Token(lexeme, TokenType.SUPER, null)); break;
            case "nil":
                list.add(new Token(lexeme, TokenType.NIL, null)); break;
            case "print":
                list.add(new Token(lexeme, TokenType.PRINT, null)); break;
            case "true":
                list.add(new Token(lexeme, TokenType.TRUE, null)); break;
            case "false":
                list.add(new Token(lexeme, TokenType.FALSE, null)); break;
            default:
                list.add(new Token(lexeme, TokenType.IDENTIFIER, null)); break;
        }
    }

    private void matchNum() {
        start = current - 1;
        while (!isAtEnt() && isNum(peek())) {
            current++;
        }
        if(!isAtEnt() && src.charAt(current) == '.') {
            current++;
            while (!isAtEnt() && isNum(peek())) {
                current++;
            }
        }
        String lexeme = src.substring(start, current);
        list.add(new Token(lexeme, TokenType.NUMBER, Double.parseDouble(lexeme)));
    }

    private char peek() {
        return src.charAt(current);
    }

    private char previous() {
        return src.charAt(current - 1);
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner("var test1 = \"aaa+-*/bb\";if(true) return 11;");
        scanner.scan();
        for (Token token : scanner.list) {
            System.out.println(token);
        }
    }
}
